Skip to main content

07 函数调用

#include <stdio.h>
int static add(int a, int b)
{
return a+b;
}


int main()
{
int x = 5;
int y = 10;
int u = add(x, y);
}
int static add(int a, int b)
{
0: 55 push rbp
1: 48 89 e5 mov rbp,rsp
4: 89 7d fc mov DWORD PTR [rbp-0x4],edi
7: 89 75 f8 mov DWORD PTR [rbp-0x8],esi
return a+b;
a: 8b 55 fc mov edx,DWORD PTR [rbp-0x4]
d: 8b 45 f8 mov eax,DWORD PTR [rbp-0x8]
10: 01 d0 add eax,edx
}
12: 5d pop rbp
13: c3 ret
0000000000000014 <main>:
int main()
{
14: 55 push rbp
15: 48 89 e5 mov rbp,rsp
18: 48 83 ec 10 sub rsp,0x10
int x = 5;
1c: c7 45 fc 05 00 00 00 mov DWORD PTR [rbp-0x4],0x5
int y = 10;
23: c7 45 f8 0a 00 00 00 mov DWORD PTR [rbp-0x8],0xa
int u = add(x, y);
2a: 8b 55 f8 mov edx,DWORD PTR [rbp-0x8]
2d: 8b 45 fc mov eax,DWORD PTR [rbp-0x4]
30: 89 d6 mov esi,edx
32: 89 c7 mov edi,eax
34: e8 c7 ff ff ff call 0 <add>
39: 89 45 f4 mov DWORD PTR [rbp-0xc],eax
3c: b8 00 00 00 00 mov eax,0x0
}
41: c9 leave
42: c3 ret

压栈(Push):push 和 mov 指令

出栈(Pop):pop 和 ret 指令

call:函数调用的跳转,在对应函数的指令执行完了之后,还要再回到函数调用的地方

把要跳回来执行的指令地址记录下来,专门设立一个“程序调用寄存器”,存储要跳转回来执行的指令地址。等到函数调用结束,从这个寄存器里取出地址,再跳转到这个记录的地址并继续执行。

CPU 里的寄存器数量有限:Intel i7 CPU 只有 16 个 64 位寄存器

栈:先进先出(LIFO,Last In First Out)的数据结构

整个函数所占用的所有内存空间,就是函数的栈帧(Stack Frame)

底在最上面,顶在最下面(栈底的内存地址是在一开始就固定的)一层层压栈之后,栈顶的内存地址在逐渐变小。

stack overflow

栈的大小是有限的,函数调用层数太多,栈里压入它存不下的内容,程序在执行的过程中就会遇到栈溢出的错误

性能优化

函数内联(Inline):把一个实际调用的函数产生的指令,直接插入到的位置,来替换对应的函数调用指令。(被调用的函数里,没有调用其他函数)

  • GCC 编译加 -O:让编译器自动优化
  • 在定义函数的地方加上 inline 的关键字:提示编译器对函数进行内联

优化结果:CPU 需要执行的指令数变少,根据地址跳转的过程不需要,不再需要压栈和出栈的过程

缺点:把可以复用的程序指令在调用它的地方完全展开了。如果一个函数在很多地方被调用,会展开很多次,整个程序占用的空间会变大。